// Text Scene Node
// (c) jimon game studio

#include "CTextSceneNode.h"

namespace je
{
	namespace scene
	{
		//! Constructor
		CTextSceneNode::CTextSceneNode(video::IRender * Render,scene::ISceneManager * SceneManager)
			:ITextSceneNode(Render,"TextSceneNode"),CurrentSceneManager(SceneManager),CurrentFontResource(NULL),CurrentSharedTexture(NULL),TextRenderOperation(NULL)
		{
		}

		//! Destructor
		CTextSceneNode::~CTextSceneNode()
		{
			SetMaterialResource(NULL);
			SetFontResource(NULL);

			if(TextRenderOperation)
				TextRenderOperation->Drop();
		}

		//! Set Text
		void CTextSceneNode::SetText(const jeStringd & Text)
		{
			CurrentText = Text;

			if(!CurrentFontResource || !CurrentSharedMaterial)
				return;

			if(CurrentTextOptions.DynamicMode)
			{
				if(TextRenderOperation)
					TextRenderOperation->Drop();

				jeVector3df * VertexPosition = NULL;
				JENEW_A(VertexPosition,jeVector3df,4*Text.GetSize())

				jeVector2df * VertexUV = NULL;
				JENEW_A(VertexUV,jeVector2df,4*Text.GetSize())

				u16 * Indexes = NULL;
				JENEW_A(Indexes,u16,6*Text.GetSize())

				jeVector3df TextPos(0);

				TextSize.SetXYZ(0,0,1.0f);

				u32 CharCounts = Text.GetSize();
				for(u32 i=0;i<CharCounts;i++)
				{
					jeschar SChar = Text[i];

					video::IFont::CharMetrics * Metrics = CurrentFontResource->GetFont()->GetCharMetrics(SChar);

					if(Metrics)
					{
						jeVector3df Kerning3d(0.0f);
						if(CurrentTextOptions.Kerning && (i>0))
						{
							jeVector2df FontKerning = CurrentFontResource->GetFont()->GetKerning(Text[i-1],Text[i]);
							Kerning3d.SetXYZ(FontKerning.X,FontKerning.Y,0.0f);
						}

						jeVector3df BaseCharPos = TextPos + jeVector3df(Metrics->ShiftX,Metrics->ShiftY,0) + Kerning3d;

						VertexPosition[i*4+0] = BaseCharPos;
						VertexPosition[i*4+1] = BaseCharPos + jeVector3df(Metrics->SizeX,0,0);
						VertexPosition[i*4+2] = BaseCharPos + jeVector3df(0,Metrics->SizeY,0);
						VertexPosition[i*4+3] = BaseCharPos + jeVector3df(Metrics->SizeX,Metrics->SizeY,0);

						TextPos.X += Metrics->SizeX + Metrics->CharShift + Kerning3d.X;

						VertexUV[i*4+0].SetXY(Metrics->U1,Metrics->V2);
						VertexUV[i*4+1].SetXY(Metrics->U2,Metrics->V2);
						VertexUV[i*4+2].SetXY(Metrics->U1,Metrics->V1);
						VertexUV[i*4+3].SetXY(Metrics->U2,Metrics->V1);

						Indexes[i*6+0] = 0 + i*4;
						Indexes[i*6+1] = 1 + i*4;
						Indexes[i*6+2] = 2 + i*4;
						Indexes[i*6+3] = 1 + i*4;
						Indexes[i*6+4] = 3 + i*4;
						Indexes[i*6+5] = 2 + i*4;

						TextSize.Y = jeMax(Metrics->ShiftY+Metrics->SizeY,TextSize.Y);
						TextSize.X += Metrics->SizeX+Metrics->CharShift+Kerning3d.X;
					}
				}

				jeVector2df TextSize2d(TextSize.X,TextSize.Y);
				f32 TextKoef = 1.0f/TextSize2d.GetDistance();
				for(u32 i=0;i<CharCounts*4;i++)
					VertexPosition[i] = (VertexPosition[i] - jeVector3df(TextSize.X /2,TextSize.Y /2,0) );//* TextKoef;
					//VertexPosition[i] = (VertexPosition[i])* TextKoef;

				NeedToRebuildMatrix = true;

				video::IHardwareBuffer * VBO = CurrentRender->CreateHardwareBuffer(video::G3DT_VERTEX);
				VBO->Lock(core::BLT_WRITE);
				VBO->Allocate(4*CharCounts*5*4);
				VBO->Write(VertexPosition,4*CharCounts*3*4);
				VBO->Merge(VertexUV,4*CharCounts*2*4,2*4,5*4);
				VBO->UnLock();

				video::IHardwareBuffer * IBO = CurrentRender->CreateHardwareBuffer(video::G3DT_INDEX);
				IBO->Lock(core::BLT_WRITE);
				IBO->Allocate(6*CharCounts*2);
				IBO->Write(Indexes,6*CharCounts*2);
				IBO->UnLock();

				JEDELETE_A(Indexes)
				JEDELETE_A(VertexUV)
				JEDELETE_A(VertexPosition)

				video::IHardwareBufferArray * VBA = CurrentRender->CreateHardwareBufferArray();
				VBA->AttachBuffer(VBO,video::HBAL_POSITION,3,4,5*4);
				VBA->AttachBuffer(VBO,video::HBAL_UV,2,4,5*4,3*4);
				VBA->AttachBuffer(IBO,video::HBAL_INDEXES,1,2);

				IBO->Drop();
				VBO->Drop();

				JENEW(TextRenderOperation,video::jeRenderOperation_Draw)
				TextRenderOperation->SetArray(VBA);
				VBA->Drop();

				if(CurrentSharedTexture && (CurrentSharedMaterial->GetMaterial()->GetTexture() != CurrentSharedTexture->Get()[0]))
					CurrentSharedMaterial->GetMaterial()->SetTexture(CurrentSharedTexture->Get()[0]);
			}
			else
			{
				jeVector2di PixelTextSize = CurrentFontResource->GetFont()->CalculateTextSize(CurrentText);
					
				TextSize.SetXYZ((f32)PixelTextSize.X,(f32)PixelTextSize.Y,1.0f);
				NeedToRebuildMatrix = true;
					
				video::IImage * Image = video::CreateImage(PixelTextSize,video::IImage::ICF_R8G8B8A8,NULL);

				CurrentFontResource->GetFont()->RenderText(Image,CurrentText,jeVector2di(0,0),CurrentTextOptions.Kerning,CurrentTextOptions.Kerning);

				video::ITexture * Texture = CurrentRender->CreateTexture(Image,video::TF_RGBA8,0);
				Image->Drop();

				CurrentSharedMaterial->GetMaterial()->SetTexture(Texture);
				Texture->Drop();
			}
		}

		//! Get Text
		const jeStringd & CTextSceneNode::GetText()
		{
			return CurrentText;
		}

		//! Set Font Resource
		void CTextSceneNode::SetFontResource(media::jeFontResource * FontResource)
		{
			if(CurrentFontResource)
			{
				if(CurrentFontResource->GetFont()->GetFontResource())
				{
					if(CurrentTextOptions.DynamicMode && CurrentSharedMaterial)
						CurrentSharedMaterial->GetMaterial()->SetTexture(NULL);
					JE_IMAGERESOURCE_SHARED_DROP(CurrentSharedTexture,CurrentRender)
				}
				CurrentFontResource->RemoveUsage();
			}
			
			CurrentFontResource = FontResource;

			if(CurrentFontResource)
			{
				CurrentFontResource->AddUsage();
				SetText(CurrentText);

				if(CurrentFontResource->GetFont()->GetFontResource())
				{
					JE_IMAGERESOURCE_SHARED_GRAB(CurrentSharedTexture,CurrentFontResource->GetFont()->GetFontResource(),CurrentRender)
					if(CurrentTextOptions.DynamicMode && CurrentSharedMaterial)
						CurrentSharedMaterial->GetMaterial()->SetTexture(CurrentSharedTexture->Get()[0]);
				}
			}

			SetText(CurrentText);
		}

		//! Get Font Resource
		media::jeFontResource * CTextSceneNode::GetFontResource()
		{
			return CurrentFontResource;
		}

		//! Set Material Resource
		void CTextSceneNode::SetMaterialResource(media::jeMaterialResource * MaterialResource,u1 UseUniqueMaterial,u1 UseUniqueTextures,u1 UseUniqueShader)
		{
			if(CurrentSharedMaterial)
				CurrentSceneManager->UnRegisterNodeFromRenderingQueue(this,CurrentSharedMaterial->GetMaterial());

			IMaterialResourceNode::SetMaterialResource(MaterialResource,UseUniqueMaterial,UseUniqueTextures,UseUniqueShader);

			if(MaterialResource)
				CurrentSceneManager->RegisterNodeInRenderingQueue(this,CurrentSharedMaterial->GetMaterial());

			NeedToRebuildMatrix = true;

			SetText(CurrentText);
		}

		//! On Update
		void CTextSceneNode::OnUpdate()
		{
			ISceneNode::OnUpdate();
		}

		//! On Render
		void CTextSceneNode::OnRender()
		{
			CurrentRender->SetMatrix(TextAbsoluteModelMatrix,video::RMT_MODEL);
			CurrentRender->SetMatrix(TextureMatrix,video::RMT_TEXTURE);
			CurrentRender->SetMaterial(CurrentSharedMaterial->GetMaterial());
			if(CurrentTextOptions.DynamicMode && TextRenderOperation)
				CurrentRender->RenderOperation(video::ROT_DRAW_TRIANGLES,TextRenderOperation);
			else
				CurrentRender->RenderOperation(video::ROT_DRAW_QUAD);
			
			ISceneNode::OnRender();
		}

		//! On Visibility Culling
		void CTextSceneNode::OnVisibilityCulling()
		{
			f32 TextRadius = jeSqr(2.0f) * TextCorrectionSize.GetMaxComponent() * AbsoluteScale.GetMaxComponent();
			InFrame = CurrentSceneManager->GetCurrentCamera()->GetFrustum()->IsSphereIn(TextAbsoluteModelMatrix.GetTranslation(),TextRadius);
			ISceneNode::OnVisibilityCulling();
		}

		//! Rebuild Transformations
		void CTextSceneNode::RebuildTransformations()
		{
			if(NeedToRebuildMatrix)
			{
				ModelMatrix.Identity();
				ModelMatrix.Scale(CurrentScale);
				ModelMatrix.Rotate(CurrentRotation);
				ModelMatrix.Translate(CurrentPosition);

				u1 TexelToPixel = false;

				switch(CurrentSceneManager->GetCurrentCamera()->GetCameraOptions().CurrentMode)
				{
				case ICameraSceneNode::CM_2D:
					{
						if(CurrentTextOptions.DynamicMode)
							TextCorrectionSize.SetXYZ(1,1,1);
						else
							TextCorrectionSize = TextSize;

						TexelToPixel = true;
						break;
					}
				case ICameraSceneNode::CM_3D:
					{
						if(CurrentTextOptions.DynamicMode)
						{
							jeVector2df Temp(TextSize.X,TextSize.Y);
							Temp = 1.0f / jeMax(TextSize.X,TextSize.Y);
							TextCorrectionSize.SetXYZ(Temp.X,Temp.Y,1);
						}
						else
						{
							jeVector2df Temp(TextSize.X,TextSize.Y);
							Temp = Temp / jeMax(TextSize.X,TextSize.Y);
							TextCorrectionSize.SetXYZ(Temp.X,Temp.Y,1);
						}
						break;
					}
				};

				TextMatrix.Identity();
				TextMatrix.Scale(TextCorrectionSize * CurrentScale);
				TextMatrix.Rotate(CurrentRotation);
				
				jeVector3df Offset(CurrentTextOptions.Origin.X,CurrentTextOptions.Origin.Y,0);
				if(!CurrentTextOptions.OriginInCenter)
					if(TexelToPixel)
						//Offset += jeVector3df((s32)(TextSize.X/2.0f),-(s32)(TextSize.Y/2.0f),0.0f);
						Offset += jeVector3df((s32)TextSize.X/2,-(s32)TextSize.Y/2,0.0f);
					else
						Offset += jeVector3df(TextSize.X/2.0f,-TextSize.Y/2.0f,0.0f);

				jeVector3df Position;
				if(TexelToPixel)
				{
					Offset += jeVector3df(0.4f,0.4f,0.0f);
					Position.SetXYZ((s32)CurrentPosition.X,(s32)CurrentPosition.Y,(s32)CurrentPosition.Z);
				}
				else
					Position = CurrentPosition;
				TextMatrix.Translate(Position + Offset);

				NeedToRebuildMatrix = false;
			}
		}

		//! Build Absolute Model Matrix
		void CTextSceneNode::BuildAbsoluteModelMatrix(const jeMatrix4f & ParentMatrix,const jeVector3df & ParentScale,u1 Changed)
		{
			ISceneNode::BuildAbsoluteModelMatrix(ParentMatrix,ParentScale,Changed);
			TextAbsoluteModelMatrix = ParentMatrix * TextMatrix;
		}

		//! Serialize
		void CTextSceneNode::Serialize(core::ISerializeStream * SerializeStream)
		{
			JE_SS_BEGIN(TextSceneNode)
			JE_SS(CurrentText)

			jeStringc FontResourceName;

			if(JE_SS_SAVE && CurrentFontResource)
				FontResourceName = CurrentFontResource->GetName();

			JE_SS(FontResourceName)

			JE_SS_SIZE(CurrentTextOptions,sizeof(TextOptions))
			JE_SS_END(TextSceneNode)

			if(JE_SS_LOAD)
				SetFontResource(JE_FONTRESOURCE(FontResourceName));

			IMaterialResourceNode::Serialize(SerializeStream);
		}
	}
}
